'''
Created on 23.10.2011

@author: alex
'''

import httplib, urllib, json, base64, hashlib, urlparse, exceptions, time, urlparse


def parseUrl(url):
	sres=urlparse.urlsplit(url)
	parts={
		'scheme':	sres.scheme,
		'host':		sres.netloc,
		'path':		sres.path,
		'query':	sres.query,
		'fragment':	sres.fragment
	}
	
	if not parts['scheme'] and not parts['host']:
		parts['scheme']='http'
		slashPos=parts['path'].find('/')
		if slashPos>-1:
			parts['host']=parts['path'][:slashPos]
			parts['path']=parts['path'][slashPos:]
		else:
			parts['host']=parts['path']
			parts['path']=''
	
	dogPos=parts['host'].find('@')
	if dogPos>-1:
		userpass=parts['host'][:dogPos]
		parts['host']=parts['host'][dogPos+1:]
		colonPos=userpass.find(':')
		if colonPos>-1:
			parts['user']=userpass[:colonPos]
			parts['password']=userpass[colonPos+1:]
	return parts

def buildUrl(parts):
	netloc=parts.get('host', '')
	if parts.get('user') and not parts.get('password'):
		netloc=parts.get('user') + '@' + netloc
	elif parts.get('user') and parts.get('password'):
		netloc=parts.get('user') + ':' + parts.get('password') + '@' + netloc
	elif not parts.get('user') and parts.get('password'):
		netloc=':' + parts.get('password') + '@' + netloc
	
	return urlparse.urlunparse((
		parts.get('scheme', ''),
		netloc,
		parts.get('path', ''),
		None,
		parts.get('query', ''),
		parts.get('fragment', '')
	))





class SacvoyageException(exceptions.Exception):
    pass

class AuthError(SacvoyageException):
    pass

class NetworkError(SacvoyageException):
    pass

class ServerError(SacvoyageException):
    pass



class _Connection:
    
    def __init__(self, url):
    	parts=parseUrl(url)
    	
    	self._host=parts['host']
    	
    	self._path=parts.get('path', '')
    	if parts.get('user'):
    		self._path=self._path.rstrip('/')+'/'+parts['user']
    		
        self._password=parts.get('password')
        
        # build canonicalUrl
        parts['scheme']=parts['user']=parts['password']=parts['query']=parts['fragment']=''
        parts['path']=self._path
        self._url=buildUrl(parts)
        
        self.login()
    
    def getUrl(self):
    	return self._url
    
    def login(self):
        self._cookie=None
        if self._password:
            try:
                headers={'Content-Type': 'application/x-www-form-urlencoded'}
                body = urllib.urlencode({'password': self._password})
                
                response=None
                for i in range(2): # try twice before give up
                    try:
                        response=self._tryRequest('POST', '', body, headers)
                    except httplib.HTTPException:
                        time.sleep(0.5)
                if not response:
                    raise NetworkError('unable to connect to ' + self._url)
                if not response['status']%100 == 2: # it's not 200 but 403 or 404 or something
                	print response['body']
                	raise AuthError('request to %s returned status %s, maybe wrong password'%(self._url, response['status']))
                self._cookie=response['headers']['set-cookie']
            except:
                self._cookie=None
                raise
            
            
    def request(self, method, url, body=None, headers={}):
        i=2
        while i>0:
            i-=1
            try:
                if self._cookie:
                    headers['Cookie']=self._cookie
                return self._tryRequest(method, url, body, headers)
            except NetworkError:
                time.sleep(0.5) # wait and try once more
            except AuthError:
                i+=1
                self.login()    # maybe session lost 
        raise Exception('Error connecting to ' + self._url)
    
    def _tryRequest(self, method, url, body=None, headers={}):
    	path=(self._path.rstrip('/') + '/' + url.lstrip('/')).rstrip('/')
        connection=httplib.HTTPConnection(self._host)
        try:
            connection.connect()
            if not headers:
                headers={}
            connection.request(method, path, body, headers)
            response=connection.getresponse()
            data=''
            block=response.read()
            while block:
                data+=block
                block=response.read()
            headers={}
            for h in response.getheaders():
                headers[h[0].lower()]=h[1]
            return {
                'status':  int(response.status),
                'headers': headers,
                'body':    data
            }
        finally:
            connection.close()
    

def _parse_json_response(data):
    try:
        return json.loads(data)
    except:
        raise ServerError('wrong server response')
    

class Collection:
    def __init__(self, sacvoyage, name):
        self._sacvoyage=sacvoyage
        self._name=name
        
    def getSacvoyage(self):
        return self._sacvoyage
    
    def read(self, _id, rev=None):
        url=self._name
        
        if isinstance(_id, unicode):
            _id=_id.encode('utf-8')
        if isinstance(rev, unicode):
            rev=rev.encode('utf-8')
            
            
        url+='/'+urllib.quote(_id, safe='/')
        if rev:
            url+='~'+urllib.quote(rev, safe='/')
        response=self.getSacvoyage()._connection.request('GET', url)
        return _parse_json_response(response['body'])
                   
    def find(self, query=None):
        params = urllib.urlencode({'_query':json.dumps(query)})
        url=self._name+'?'+params if params else self._name
        response=self.getSacvoyage()._connection.request('GET', url)
        return _parse_json_response(response['body'])
            
    def write(self, documents):
        data=json.dumps(documents)
        response=self.getSacvoyage()._connection.request('POST', self._name, data)
        return _parse_json_response(response['body'])
            
    def delete(self, query):
        params = urllib.urlencode({'_query':json.dumps(query)})
        response=self.getSacvoyage()._connection.request('DELETE', self._name+'?'+params)
        return _parse_json_response(response['body'])


class Sacvoyage:
    def __init__(self, url):
        self._connection=_Connection(url)
    
    def getUrl(self):
        return self._connection.getUrl()
    
    def getCollectionNames(self):
        response=self._connection.request('GET', "*")
        if response['status']!=200:
            raise AuthError('server returned status %s, maybe wrong password'%response['status'])
        
        try:
            return json.loads(response['body'])
        except:
            print 'status: ', response['status']
            example=response['body'] if len(response['body'])<100 else response['body'][:100]+'...'
            raise ServerError('wrong server response: '+example)
            
    def getCollection(self, name):
        if isinstance(name, unicode):
            name=name.encode('utf-8')
        return Collection(self, name)



